const std = @import("std");
const sys = @import("syscall.zig");
const os = @import("os");
const c_str = sys.c_str;
const StackIterator = os.debug.StackIterator;

pub const color = os.lib.color;
pub const heap = @import("heap.zig");
pub const File = @import("File.zig");

pub const panic = std.debug.FullPanic(panicImpl);

fn panicImpl(msg: []const u8, ra: ?usize) noreturn {
    @branchHint(.cold);
    print("{f}PANIC: {f}{s}!\n", .{ color.yellow.with("r"), color.red.with("i"), msg });

    var wait_for: ?usize = ra orelse @returnAddress();
    var it: StackIterator = .init();

    print("{f}backtrace:", .{color.grey.with("ri")});
    while (true) switch (it.next()) {
        .end => break,
        .frame => |addr| {
            if (wait_for) |target| {
                if (addr != target) continue;
                wait_for = null;
            }
            print(" 0x{x}", .{addr});
        },
    };
    print("\n{f}", .{color.reset});

    @trap();
}

pub fn print(comptime format: []const u8, args: anytype) void {
    fprint(1, format, args);
}

pub fn fprint(fd: sys.fd_t, comptime format: []const u8, args: anytype) void {
    var w: File.Writer = .init(fd);
    w.io_w.print(format, args) catch return;
}

/// Prepare arguments and execute external program with full PATH search.
/// Converts tokens to C-style argument array and handles executable path resolution.
pub fn execvp(file: c_str, args: [:null]const ?c_str) !void {
    const program: c_str = if (file[0] == '/' or std.mem.startsWith(u8, std.mem.sliceTo(file, 0), "./"))
        file
    else search: {
        var buffer: [os.fs.MAXPATH]u8 = undefined;
        inline for (.{ "/bin", "/tests" }) |prefix| {
            const path = try std.fmt.bufPrintSentinel(&buffer, prefix ++ "/{s}", .{file}, 0);
            if (sys.access(path)) break :search path;
        }
        return error.FileNotFound;
    };

    const argv = args[0 .. args.len + 1];
    _ = sys.exec(program, @ptrCast(argv));
}
